;; Licensed to the .NET Foundation under one or more agreements.
;; The .NET Foundation licenses this file to you under the MIT license.
;; See the LICENSE file in the project root for more information.

#include "AsmMacros.h"

#ifdef _DEBUG
#define TRASH_SAVED_ARGUMENT_REGISTERS
#endif

#ifdef TRASH_SAVED_ARGUMENT_REGISTERS
        EXTERN RhpIntegerTrashValues
        EXTERN RhpFpTrashValues
#endif ;; TRASH_SAVED_ARGUMENT_REGISTERS

#define COUNT_ARG_REGISTERS (4)
#define INTEGER_REGISTER_SIZE (4)
#define ARGUMENT_REGISTERS_SIZE (COUNT_ARG_REGISTERS * INTEGER_REGISTER_SIZE)

;; Largest return block is 4 doubles
#define RETURN_BLOCK_SIZE (32) 

#define COUNT_FLOAT_ARG_REGISTERS (8)
#define FLOAT_REGISTER_SIZE (8)
#define FLOAT_ARG_REGISTERS_SIZE (COUNT_FLOAT_ARG_REGISTERS * FLOAT_REGISTER_SIZE)

#define PUSHED_LR_SIZE (4)
#define PUSHED_R11_SIZE (4)

;;
;; From CallerSP to ChildSP, the stack frame is composed of the following adjacent regions:
;;
;;      ARGUMENT_REGISTERS_SIZE
;;      RETURN_BLOCK_SIZE
;;      FLOAT_ARG_REGISTERS_SIZE
;;      PUSHED_LR
;;      PUSHED_R11
;;

#define DISTANCE_FROM_CHILDSP_TO_RETURN_BLOCK (PUSHED_R11_SIZE + PUSHED_LR_SIZE + FLOAT_ARG_REGISTERS_SIZE)

        TEXTAREA

;;
;; RhpUniversalTransition
;; 
;; At input to this function, r0-3, d0-7 and the stack may contain any number of arguments.
;;
;; In addition, there are 2 extra arguments passed in the RED ZONE (8 byte negative space
;; off of sp).
;; sp-4 will contain the managed function that is to be called by this transition function
;; sp-8 will contain the pointer sized extra argument to the managed function
;;
;; When invoking the callee:
;;
;;  r0 shall contain a pointer to the TransitionBlock
;;  r1 shall contain the value that was in sp-8 at entry to this function
;;
;; Frame layout is:
;;
;;  {StackPassedArgs}                           ChildSP+078     CallerSP+000
;;  {IntArgRegs (r0-r3) (0x10 bytes)}           ChildSP+068     CallerSP-010
;;  {ReturnBlock (0x20 bytes)}                  ChildSP+048     CallerSP-030
;;   -- The base address of the Return block is the TransitionBlock pointer, the floating point args are
;;      in the neg space of the TransitionBlock pointer.  Note that the callee has knowledge of the exact
;;      layout of all pieces of the frame that lie at or above the pushed floating point registers.
;;  {FpArgRegs (d0-d7) (0x40 bytes)}            ChildSP+008     CallerSP-070
;;  {PushedLR}                                  ChildSP+004     CallerSP-074
;;  {PushedR11}                                 ChildSP+000     CallerSP-078
;;
;; NOTE: If the frame layout ever changes, the C++ UniversalTransitionStackFrame structure
;; must be updated as well.
;;
;; NOTE: The callee receives a pointer to the base of the ReturnBlock, and the callee has
;; knowledge of the exact layout of all pieces of the frame that lie at or above the pushed
;; FpArgRegs.
;;
;; NOTE: The stack walker guarantees that conservative GC reporting will be applied to
;; everything between the base of the ReturnBlock and the top of the StackPassedArgs.
;;

        MACRO 
        UNIVERSAL_TRANSITION $FunctionName

        NESTED_ENTRY Rhp$FunctionName
        ;; Save argument registers (including floating point) and the return address.
        ;; NOTE: While we do that, capture the two arguments in the red zone into r12 and r3.
        PROLOG_NOP  ldr r12, [sp, #-4]              ; Capture first argument from red zone into r12
        PROLOG_PUSH {r3}                            ; Push r3
        PROLOG_NOP  ldr r3, [sp, #-4]               ; Capture second argument from red zone into r3
        PROLOG_PUSH {r0-r2}                         ; Push the rest of the registers
        PROLOG_STACK_ALLOC RETURN_BLOCK_SIZE        ; Save space a buffer to be used to hold return buffer data.
        PROLOG_VPUSH {d0-d7}                        ; Capture the floating point argument registers
        PROLOG_PUSH {r11,lr}                        ; Save caller's frame chain pointer and PC

        ;; Setup the arguments to the transition thunk.
        mov         r1, r3

#ifdef TRASH_SAVED_ARGUMENT_REGISTERS

        ;; Before calling out, trash all of the argument registers except the ones (r0, r1) that
        ;; hold outgoing arguments.  All of these registers have been saved to the transition
        ;; frame, and the code at the call target is required to use only the transition frame
        ;; copies when dispatching this call to the eventual callee.

        ldr         r3, =RhpFpTrashValues
        vldr        d0, [r3, #(0 * 8)]
        vldr        d1, [r3, #(1 * 8)]
        vldr        d2, [r3, #(2 * 8)]
        vldr        d3, [r3, #(3 * 8)]
        vldr        d4, [r3, #(4 * 8)]
        vldr        d5, [r3, #(5 * 8)]
        vldr        d6, [r3, #(6 * 8)]
        vldr        d7, [r3, #(7 * 8)]

        ldr         r3, =RhpIntegerTrashValues
        ldr         r2, [r3, #(2 * 4)]
        ldr         r3, [r3, #(3 * 4)]

#endif // TRASH_SAVED_ARGUMENT_REGISTERS

        ;; Make the ReturnFromUniversalTransition alternate entry 4 byte aligned
        ALIGN 4
        add         r0, sp, #DISTANCE_FROM_CHILDSP_TO_RETURN_BLOCK  ;; First parameter to target function is a pointer to the return block
        blx         r12

        EXPORT_POINTER_TO_ADDRESS PointerToReturnFrom$FunctionName

        ; We cannot make the label public as that tricks DIA stackwalker into thinking
        ; it's the beginning of a method. For this reason we export an auxiliary variable
        ; holding the address instead.

        ;; Move the result (the target address) to r12 so it doesn't get overridden when we restore the
        ;; argument registers. Additionally make sure the thumb2 bit is set.
        orr     r12, r0, #1

        ;; Restore caller's frame chain pointer and PC.
        EPILOG_POP {r11,lr}

        ;; Restore the argument registers.
        EPILOG_VPOP {d0-d7}
        EPILOG_STACK_FREE RETURN_BLOCK_SIZE        ; pop return block conservatively reported area
        EPILOG_POP {r0-r3}

        ;; Tailcall to the target address.
        EPILOG_BRANCH_REG r12

        NESTED_END Rhp$FunctionName

        MEND

        ; To enable proper step-in behavior in the debugger, we need to have two instances
        ; of the thunk. For the first one, the debugger steps into the call in the function, 
        ; for the other, it steps over it.
        UNIVERSAL_TRANSITION UniversalTransition
        UNIVERSAL_TRANSITION UniversalTransition_DebugStepTailCall

        END
